using System;
using System.Collections;
using System.Data.SqlClient;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;

namespace BoxBinary.AspectCache.Core.ErrorLogging
{
    /// <summary>
    /// 
    /// </summary>
    public abstract class ErrorLoggerBase : ILogProvider
    {
        public const string MD_Build = "BUILD";
        public const string MD_BuildDate = "BUILDDATE";
        public const string MD_ExceptionMsg = "EXCEPTION_MSG";
        public const string MD_Exceptiontype = "EXCEPTION_TYPE";
        public const string MD_hiddenFromuser = "HIDDEN_FROM_USER";
        public const string MD_Host = "HOST";
        public const string MD_MachineName = "MACHINE_NAME";
        public const string MD_Uri = "URI";
        public const string MD_USERID = "USERID";

        private string _body;
        private ErrorMetaData _errorMetaData;

        [CLSCompliant(false)] protected Exception _ex;

        private string _title;

        /// <summary>
        /// Gets the inner exception.
        /// </summary>
        /// <value>The inner exception.</value>
        internal Exception InnerException
        {
            get { return _ex; }
        }

        #region ILogProvider Members

        public abstract void SaveLog();

        /// <summary>
        /// Resets the properties to their default values.
        /// </summary>
        public void Clear()
        {
            _body = "";
            _ex = null;
            _errorMetaData = null;
            _title = "";
        }

        /// <summary>
        /// The body of the log. This should automatically be populated during SaveLog()
        /// </summary>
        /// <value></value>
        public string Body
        {
            get { return _body; }
            set { _body = value; }
        }

        /// <summary>
        /// The title of the log. If null, during SaveLog(), this should automatically be populated.
        /// </summary>
        /// <value></value>
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        /// <summary>
        /// The Exception to log
        /// </summary>
        /// <value></value>
        [Obsolete("Use StartNewLog(Exception, bool) instead", false)]
        public Exception Exception
        {
            get { return _ex; }
            set { _ex = value; }
        }

        /// <summary>
        /// Whether the generated error was handled by the application and hidden from the user
        /// </summary>
        /// <value></value>
        public bool HiddenFromUser
        {
            get
            {
                string val = ErrorMetaData[MD_hiddenFromuser];
                if (string.IsNullOrEmpty(val))
                    val = "false";
                return Convert.ToBoolean(val);
            }
            set { ErrorMetaData[MD_hiddenFromuser] = value.ToString(); }
        }

        /// <summary>
        /// The error metadata
        /// </summary>
        /// <value></value>
        public ErrorMetaData ErrorMetaData
        {
            get
            {
                if (_errorMetaData == null)
                    _errorMetaData = new ErrorMetaData();
                return _errorMetaData;
            }
            set { _errorMetaData = value; }
        }

        /// <summary>
        /// Whether logging is enabled in the config file
        /// </summary>
        /// <value></value>
        public bool IsLoggingEnabled
        {
            get
            {
                if (ConfigSection.Current != null)
                    return ConfigSection.Current.IsLoggingEnabled;
                return false;
            }
        }

        /// <summary>
        /// Resets the properties to their default values, sets the Exception property and notifies the PeriodicErrorSummary
        /// that a new error has been thrown.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="hiddenFromUser"></param>
        public void StartNewLog(Exception e, bool hiddenFromUser)
        {
            StartNewLog(e, hiddenFromUser, false);
        }

        /// <summary>
        /// Resets the properties to their default values, sets the Exception property and notifies the PeriodicErrorSummary
        /// that a new error has been thrown if <paramref name="excludeFromErrorThresholdChecking"/> is true.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="hiddenFromUser"></param>
        /// <param name="excludeFromErrorThresholdChecking"></param>
        public void StartNewLog(Exception e, bool hiddenFromUser, bool excludeFromErrorThresholdChecking)
        {
            Clear();
            _ex = e;
            HiddenFromUser = hiddenFromUser;
            PopulateFromException();
            if (!excludeFromErrorThresholdChecking)
                PeriodicErrorSummaryThreadWorker.InMemoryExceptionCache.Add(new ErrorSummary(_ex, ErrorMetaData));
        }

        #endregion

        /// <summary>
        /// Formats the error styles.
        /// </summary>
        /// <returns></returns>
        protected static string FormatErrorStyles()
        {
            return "<style>body, p, h1, h2, h3, td, tr {font-family: Verdana;}</style>";
        }

        /// <summary>
        /// Formats the error footer.
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <returns></returns>
        protected static string FormatErrorFooter(Exception ex)
        {
            return
                string.Format("<h2>Stack Trace:</h2><p>{0}</p>",
                              ex.ToString().Replace(Environment.NewLine, "<br>").Replace("\t",
                                                                                         "&nbsp;&nbsp;&nbsp;&nbsp;").
                                  Replace(" ", "&nbsp;"));
        }

        /// <summary>
        /// Recurses the format error.
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <returns></returns>
        public static string RecurseFormatError(Exception ex)
        {
            if (ex == null)
                return string.Empty;

            var sb = new StringBuilder();
            sb.Append("<ul><li>");
            if (ex is SqlException)
                sb.Append(FormatSQLException((SqlException) ex));
            else if (ex is HttpException)
                sb.Append(FormatHttpException((HttpException) ex));
            else if (ex is HttpUnhandledException)
                sb.Append(FormatHttpUnhandledException((HttpUnhandledException) ex));
            else if (ex is ArgumentException)
                sb.Append(FormatArgumentException((ArgumentException) ex));
            else
            {
                sb.Append(FormatExceptionHeader(ex));
                sb.Append("<p><i>No special formatting is implemented for this Exception type</i></p>");
            }
            sb.Append(RecurseFormatError(ex.InnerException));
            sb.Append("</li></ul>");
            return sb.ToString();
        }

        protected static string FormatExceptionHeader(Exception ex)
        {
            return
                string.Format("<h2>{0}<br/><i><small><small>Type: {1}</small></small></i></h2>", ex.Message,
                              ex.GetType().FullName);
        }

        protected static string FormatArgumentException(ArgumentException ex)
        {
            return FormatExceptionHeader(ex) + ex.Message;
        }

        protected static string FormatHttpException(HttpException ex)
        {
            return FormatExceptionHeader(ex) + ex.GetHtmlErrorMessage();
        }

        protected static string FormatHttpUnhandledException(HttpUnhandledException ex)
        {
            return FormatExceptionHeader(ex) + ex.GetHtmlErrorMessage();
        }

        protected static string FormatSQLException(SqlException ex)
        {
            var sb = new StringBuilder();
            const string template =
                "<ul><li><b>Message: </b>{0}</li><li><b>Line: </b>{1}</li><li><b>Source: </b>{2}</li><li><b>Procedure: </b>{3}</li><li><b>Server: </b>{4}</li></ul>";

            foreach (SqlError sqlError in ex.Errors)
            {
                sb.AppendFormat(template, sqlError.Message, sqlError.LineNumber, sqlError.Source, sqlError.Procedure,
                                sqlError.Server);
            }

            return string.Concat(FormatExceptionHeader(ex), sb.ToString());
        }

        /// <summary>
        /// Generates the lists.
        /// </summary>
        /// <returns></returns>
        public string GenerateLists()
        {
            var sb = new StringBuilder();
            try
            {
                sb.Append("<h1>Error MetaData</h1>");
                sb.Append("<p>");
                if (ErrorMetaData.Count == 0)
                    sb.Append("Collection was empty.");
                else
                {
                    foreach (var kValue in ErrorMetaData)
                    {
                        sb.AppendFormat("<b>{0}</b>", kValue.Key);
                        sb.Append(":&nbsp;&nbsp;");
                        sb.Append(kValue.Value);
                        sb.Append("<br/>");
                    }
                }
                sb.Append("</p>");

                sb.Append("<h1>Developer-defined Metadata</h1>");
                sb.Append("<p>");
                if (_ex.Data.Count == 0)
                    sb.Append("Collection was empty.");
                else
                {
                    foreach (DictionaryEntry kValue in _ex.Data)
                    {
                        sb.AppendFormat("<b>{0}</b>", kValue.Key);
                        sb.Append(":&nbsp;&nbsp;");
                        sb.Append(kValue.Value.ToString());
                        sb.Append("<br/>");
                    }
                }
                sb.Append("</p>");

                if (HttpContext.Current != null)
                {
                    sb.Append("<h1>POST data (Request.Form)</h1>");
                    sb.Append("<p>");
                    if (HttpContext.Current.Request.Form.Count == 0)
                        sb.Append("Collection was empty.");
                    else
                    {
                        foreach (string key in HttpContext.Current.Request.Form.Keys)
                        {
                            sb.AppendFormat("<b>{0}</b>", key);
                            sb.Append(":&nbsp;&nbsp;");
                            sb.Append(HttpContext.Current.Request.Form[key]);
                            sb.Append("<br/>");
                        }
                    }
                    sb.Append("</p>");

                    sb.Append("<h1>GET data (Request.QueryString)</h1>");
                    sb.Append("<p>");
                    if (HttpContext.Current.Request.QueryString.Count == 0)
                        sb.Append("Collection was empty.");
                    else
                    {
                        foreach (string key in HttpContext.Current.Request.QueryString.Keys)
                        {
                            sb.AppendFormat("<b>{0}</b>", key);
                            sb.Append(":&nbsp;&nbsp;");
                            sb.Append(HttpContext.Current.Request.QueryString[key]);
                            sb.Append("<br/>");
                        }
                    }
                    sb.Append("</p>");

                    sb.Append("<h1>Server Variables</h1>");
                    sb.Append("<p>");
                    if (HttpContext.Current.Request.ServerVariables.Count == 0)
                        sb.Append("Collection was empty.");
                    else
                    {
                        foreach (string key in HttpContext.Current.Request.ServerVariables.Keys)
                        {
                            sb.AppendFormat("<b>{0}</b>", key);
                            sb.Append(":&nbsp;&nbsp;");
                            sb.Append(HttpContext.Current.Request.ServerVariables[key]);
                            sb.Append("<br/>");
                        }
                    }
                    sb.Append("</p>");
                }
                else
                    sb.Append("<h2>Error was not within a normal WebRequest context (HttpContext.Current was null)</h2>");
            }
            catch
            {
                return string.Empty;
            }
            return sb.ToString();
        }

        protected void PopulateFromException()
        {
            PopulateFromException(Assembly.GetCallingAssembly());
        }

        protected void PopulateFromException(Assembly callingAssembly)
        {
            if (_ex != null)
            {
                PopulateMetaData(callingAssembly);

                string summary =
                    string.Format("{0}Error ('{1}') on {2}",
                                  (string.IsNullOrEmpty(ErrorMetaData[MD_hiddenFromuser]) ? "" : "[HIDDEN] "),
                                  _ex.Message, ErrorMetaData[MD_Host]);

                Body = string.Format(
                                        "<h1>Debugging Email from server {0}<br><small><small><i>{1} v{2} built on {3}</i></small></small></h1><p>{4}</p>{5}",
                                        ErrorMetaData[MD_MachineName],
                                        callingAssembly.GetName().Name,
                                        ErrorMetaData[MD_Build],
                                        GetAssemblyDate(callingAssembly),
                                        summary,
                                        // Append Body if it was already set
                                        (string.IsNullOrEmpty(Body) ? "" : string.Format("<p>{0}</p>", Body)));

                if (string.IsNullOrEmpty(Title))
                    Title = summary;
                Body += RecurseFormatError(_ex) + FormatErrorFooter(_ex) + GenerateLists() +
                        FormatErrorStyles();
            }
        }

        protected void PopulateMetaData(Assembly callingAssembly)
        {
            ErrorMetaData[MD_MachineName] = Environment.MachineName;
            ErrorMetaData[MD_Build] = GetAssemblyVersion(callingAssembly);
            if (HttpContext.Current != null)
            {
                ErrorMetaData[MD_Host] = HttpContext.Current.Request.Url.Host;
                ErrorMetaData[MD_Uri] = HttpContext.Current.Request.Url.ToString();
            }
            else
            {
                ErrorMetaData[MD_Host] = Environment.MachineName;
                ErrorMetaData[MD_Uri] = callingAssembly.FullName;
                ErrorMetaData[MD_Build] = "N/A";
            }
            if (_ex != null)
            {
                string exName = _ex.GetType().FullName;
                string exMessage = _ex.Message;
                if (_ex is HttpUnhandledException)
                {
                    if (!(_ex.InnerException == null))
                    {
                        exName = _ex.InnerException.GetType().FullName;
                        exMessage = _ex.InnerException.Message;
                    }
                }
                ErrorMetaData[MD_Exceptiontype] = exName;
                ErrorMetaData[MD_ExceptionMsg] = exMessage;
            }
        }

        /// <summary>
        /// Gets the assembly date.
        /// </summary>
        /// <param name="fromAssembly">From assembly.</param>
        /// <returns></returns>
        private static string GetAssemblyDate(Assembly fromAssembly)
        {
            AssemblyName aName = fromAssembly.GetName();
            string n = aName.CodeBase;
            var u = new Uri(n);
            DateTime d = File.Exists(u.LocalPath) ? File.GetLastWriteTime(u.LocalPath) : DateTime.Now;
            n = d.ToString("yyyyMMddHHmm");
            return n;
        }

        /// <summary>
        /// Gets the assembly version.
        /// </summary>
        /// <param name="fromAssembly">From assembly.</param>
        /// <returns></returns>
        private static string GetAssemblyVersion(Assembly fromAssembly)
        {
            return fromAssembly.GetName().Version.ToString();
        }
    }
}